home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Visual Cafe 3
/
Visual Cafe 3.ISO
/
Vcafe
/
JFC.bin
/
DefaultCaret.java
< prev
next >
Wrap
Text File
|
1998-06-30
|
21KB
|
779 lines
/*
* @(#)DefaultCaret.java 1.52 98/04/09
*
* Copyright (c) 1997 Sun Microsystems, Inc. All Rights Reserved.
*
* This software is the confidential and proprietary information of Sun
* Microsystems, Inc. ("Confidential Information"). You shall not
* disclose such Confidential Information and shall use it only in
* accordance with the terms of the license agreement you entered into
* with Sun.
*
* SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
* SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
* SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
* THIS SOFTWARE OR ITS DERIVATIVES.
*
*/
package com.sun.java.swing.text;
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import com.sun.java.swing.*;
import com.sun.java.swing.event.*;
import com.sun.java.swing.plaf.*;
/**
* An implementation of Caret for a view that maps over
* the entire portion of the model represented (i.e. there are no
* holes in the area represented) and renders the insert position
* as a vertical line.
*
* The foreground color of the component is the color of the caret
* and the background color of the component is the color of the
* selections made by moving the caret. The Highlighter implementation
* of the associated UI is used to actually render the selection.
* <p>
* Warning: serialized objects of this class will not be compatible with
* future swing releases. The current serialization support is appropriate
* for short term storage or RMI between Swing1.0 applications. It will
* not be possible to load serialized Swing1.0 objects with future releases
* of Swing. The JDK1.2 release of Swing will be the compatibility
* baseline for the serialized form of Swing objects.
*
* @author Timothy Prinzing
* @version 1.52 04/09/98
* @see Caret
*/
public class DefaultCaret implements Caret, Serializable, FocusListener, MouseListener, MouseMotionListener {
/**
* Constructs a default caret.
*/
public DefaultCaret() {
}
/**
* Gets the editor component that this caret is for.
*
* @return the component
*/
protected final JTextComponent getComponent() {
return component;
}
/**
* Damages the area surrounding the caret to cause
* it to be repainted. If paint() is reimplemented,
* this method should also be reimplemented.
*
* @param r the current location of the caret
* @see #paint
*/
protected void damage(Rectangle r) {
if (r != null) {
component.repaint(r.x - 1, r.y, 3, r.height);
}
}
/**
* Scrolls the associated view (if necessary) to make
* the caret visible. Since how this should be done
* is somewhat of a policy, this method can be
* reimplemented to change the behavior. By default
* the scrollRectToVisible method is called on the
* associated component.
*
* @param nloc the new position to scroll to
*/
protected void adjustVisibility(Rectangle nloc) {
SwingUtilities.invokeLater(new SafeScroller(nloc));
}
/**
* Gets the painter for the Highlighter.
*
* @return the painter
*/
protected Highlighter.HighlightPainter getSelectionPainter() {
Highlighter.HighlightPainter p = new DefaultHighlighter.DefaultHighlightPainter(
component.getSelectionColor());
return p;
}
/**
* Tries to set the position of the caret from
* the coordinates of a mouse event, using viewToModel().
*
* @param e the mouse event
*/
protected void positionCaret(MouseEvent e) {
Point pt = new Point(e.getX(), e.getY());
int pos = component.viewToModel(pt);
if (pos >= 0) {
setDot(pos);
// clear the prefferred caret position
// see: JCaret's UpAction/DownAction
setMagicCaretPosition(null);
}
}
/**
* Tries to move the position of the caret from
* the coordinates of a mouse event, using viewToModel().
* This will cause a selection if the dot and mark
* are different.
*
* @param e the mouse event
*/
protected void moveCaret(MouseEvent e) {
Point pt = new Point(e.getX(), e.getY());
int pos = component.viewToModel(pt);
if (pos >= 0) {
moveDot(pos);
}
}
// --- FocusListener methods --------------------------
/**
* Called when the component containing the caret gains
* focus. This is implemented to set the caret to visible
* if the component is editable, and sets the selection
* to visible.
*
* @param e the focus event
* @see FocusListener#focusGained
*/
public void focusGained(FocusEvent e) {
if (component.isEditable()) {
setVisible(true);
}
//setSelectionVisible(true);
}
/**
* Called when the component containing the caret loses
* focus. This is implemented to set the caret to visibility
* to false, and to set the selection visibility to false.
*
* @param e the focus event
* @see FocusListener#focusLost
*/
public void focusLost(FocusEvent e) {
setVisible(false);
//setSelectionVisible(false);
}
// --- MouseListener methods -----------------------------------
/**
* Called when the mouse is clicked. A double click selects a word,
* and a triple click the current line.
*
* @param e the mouse event
* @see MouseListener#mouseClicked
*/
public void mouseClicked(MouseEvent e) {
if(e.getClickCount() == 2) {
Action a = new DefaultEditorKit.SelectWordAction();
a.actionPerformed(null);
} else if(e.getClickCount() == 3) {
Action a = new DefaultEditorKit.SelectLineAction();
a.actionPerformed(null);
}
}
/**
* Requests focus on the associated
* text component, and tries to set the cursor position.
*
* @param e the mouse event
* @see MouseListener#mousePressed
*/
public void mousePressed(MouseEvent e) {
positionCaret(e);
if (component.isEnabled()) {
component.requestFocus();
}
}
/**
* Called when the mouse is released.
*
* @param e the mouse event
* @see MouseListener#mouseReleased
*/
public void mouseReleased(MouseEvent e) {
}
/**
* Called when the mouse enters a region.
*
* @param e the mouse event
* @see MouseListener#mouseEntered
*/
public void mouseEntered(MouseEvent e) {
}
/**
* Called when the mouse exits a region.
*
* @param e the mouse event
* @see MouseListener#mouseExited
*/
public void mouseExited(MouseEvent e) {
}
// --- MouseMotionListener methods -------------------------
/**
* Moves the caret position
* according to the mouse pointer's current
* location. This effectively extends the
* selection.
*
* @param e the mouse event
* @see MouseMotionListener#mouseDragged
*/
public void mouseDragged(MouseEvent e) {
moveCaret(e);
}
/**
* Called when the mouse is moved.
*
* @param e the mouse event
* @see MouseMotionListener#mouseMoved
*/
public void mouseMoved(MouseEvent e) {
}
// ---- Caret methods ---------------------------------
/**
* Renders the caret as a vertical line. If this is reimplemented
* the damage method should also be reimplemented as it assumes the
* shape of the caret is a vertical line. Sets the caret color to
* the value returned by getCaretColor().
*
* @param g the graphics context
* @see #damage
*/
public void paint(Graphics g) {
if(isVisible()) {
try {
TextUI mapper = component.getUI();
Rectangle r = mapper.modelToView(dot);
g.setColor(component.getCaretColor());
g.drawLine(r.x, r.y, r.x, r.y + r.height - 1);
} catch (BadLocationException e) {
// can't render I guess
//System.err.println("Can't render cursor");
}
}
}
/**
* Called when the UI is being installed into the
* interface of a JTextComponent. This can be used
* to gain access to the model that is being navigated
* by the implementation of this interface. Sets the dot
* and mark to 0, and establishes document, property change,
* focus, mouse, and mouse motion listeners.
*
* @param c the component
* @see Caret#install
*/
public void install(JTextComponent c) {
component = c;
Document doc = c.getDocument();
dot = mark = 0;
if (doc != null) {
doc.addDocumentListener(updateHandler);
}
c.addPropertyChangeListener(updateHandler);
c.addFocusListener(this);
c.addMouseListener(this);
c.addMouseMotionListener(this);
}
/**
* Called when the UI is being removed from the
* interface of a JTextComponent. This is used to
* unregister any listeners that were attached.
*
* @param c the component
* @see Caret#deinstall
*/
public void deinstall(JTextComponent c) {
c.removeMouseListener(this);
c.removeMouseMotionListener(this);
c.removeFocusListener(this);
c.removePropertyChangeListener(updateHandler);
Document doc = c.getDocument();
if (doc != null) {
doc.removeDocumentListener(updateHandler);
}
component = null;
if (flasher != null) {
flasher.stop();
}
}
/**
* Adds a listener to track whenever the caret position has
* been changed.
*
* @param l the listener
* @see Caret#addChangeListener
*/
public void addChangeListener(ChangeListener l) {
listenerList.add(ChangeListener.class, l);
}
/**
* Removes a listener that was tracking caret position changes.
*
* @param l the listener
* @see Caret#removeChangeListener
*/
public void removeChangeListener(ChangeListener l) {
listenerList.remove(ChangeListener.class, l);
}
/**
* Notifies all listeners that have registered interest for
* notification on this event type. The event instance
* is lazily created using the parameters passed into
* the fire method. The listener list is processed last to first.
*
* @see EventListenerList
*/
protected void fireStateChanged() {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length-2; i>=0; i-=2) {
if (listeners[i]==ChangeListener.class) {
// Lazily create the event:
if (changeEvent == null)
changeEvent = new ChangeEvent(this);
((ChangeListener)listeners[i+1]).stateChanged(changeEvent);
}
}
}
/**
* Changes the selection visibility.
*
* @param vis the new visibility
*/
public void setSelectionVisible(boolean vis) {
if (vis) {
// show
if ((selectionTag == null) && (dot != mark)) {
Highlighter h = component.getHighlighter();
int p0 = Math.min(dot, mark);
int p1 = Math.max(dot, mark);
Highlighter.HighlightPainter p = getSelectionPainter();
try {
selectionTag = h.addHighlight(p0, p1, p);
} catch (BadLocationException bl) {
selectionTag = null;
}
}
} else {
// hide
if (selectionTag != null) {
Highlighter h = component.getHighlighter();
h.removeHighlight(selectionTag);
selectionTag = null;
}
}
}
/**
* Checks whether the current selection is visible.
*
* @return true if the selection is visible
*/
public boolean isSelectionVisible() {
return (selectionTag != null);
}
/**
* Determines if the caret is currently visible.
*
* @return true if visible else false
* @see Caret#isVisible
*/
public boolean isVisible() {
return visible;
}
/**
* Sets the caret visibility, and repaints the caret.
*
* @param e the visibility specifier
* @see Caret#setVisible
*/
public void setVisible(boolean e) {
TextUI mapper = component.getUI();
Document doc = component.getDocument();
if ((visible != e) && (doc != null) && (mapper != null)) {
// repaint the caret
try {
Rectangle loc = mapper.modelToView(dot);
damage(loc);
} catch (BadLocationException badloc) {
// hmm... not legally positioned
}
}
visible = e;
if (flasher != null) {
if (visible) {
flasher.start();
} else {
flasher.stop();
}
}
}
/**
* Sets the caret blink rate.
*
* @param rate the rate in milliseconds, 0 to stop blinking
* @see Caret#setBlinkRate
*/
public void setBlinkRate(int rate) {
if (rate != 0) {
if (flasher == null) {
flasher = new Timer(rate, updateHandler);
}
flasher.setDelay(rate);
} else {
if (flasher != null) {
flasher.stop();
flasher.removeActionListener(updateHandler);
flasher = null;
}
}
}
/**
* Gets the caret blink rate.
*
* @returns the delay in milliseconds. If this is
* zero the caret will not blink.
* @see Caret#getBlinkRate
*/
public int getBlinkRate() {
return (flasher == null) ? 0 : flasher.getDelay();
}
/**
* Fetches the current position of the caret.
*
* @return the position >= 0
* @see Caret#getDot
*/
public int getDot() {
return dot;
}
/**
* Fetches the current position of the mark. If there is a selection,
* the dot and mark will not be the same.
*
* @return the position >= 0
* @see Caret#getMark
*/
public int getMark() {
return mark;
}
/**
* Sets the caret position and mark to some position. This
* implicitly sets the selection range to zero.
*
* @param dot the position >= 0
* @see Caret#setDot
*/
public void setDot(int dot) {
// move dot, if it changed
Document doc = component.getDocument();
if (doc != null) {
dot = Math.min(dot, doc.getLength());
}
dot = Math.max(dot, 0);
mark = dot;
if (this.dot != dot || selectionTag != null) {
changeCaretPosition(dot);
}
if (selectionTag != null) {
Highlighter h = component.getHighlighter();
h.removeHighlight(selectionTag);
selectionTag = null;
}
}
/**
* Moves the caret position to some other position.
*
* @param dot the position >= 0
* @see Caret#moveDot
*/
public void moveDot(int dot) {
if (dot != this.dot) {
changeCaretPosition(dot);
Highlighter h = component.getHighlighter();
int p0 = Math.min(dot, mark);
int p1 = Math.max(dot, mark);
try {
if (selectionTag != null) {
h.changeHighlight(selectionTag, p0, p1);
} else {
Highlighter.HighlightPainter p = getSelectionPainter();
selectionTag = h.addHighlight(p0, p1, p);
}
} catch (BadLocationException e) {
throw new StateInvariantError("Bad caret position");
}
}
}
// ---- local methods --------------------------------------------
/**
* Sets the caret position (dot) to a new location. This
* causes the old and new location to be repainted. It
* also makes sure that the caret is within the visible
* region of the view, if the view is scrollable.
*/
void changeCaretPosition(int dot) {
TextUI mapper = component.getUI();
Document doc = component.getDocument();
if ((mapper != null) && (doc != null)) {
// repaint the old position
Rectangle oldLoc;
try {
oldLoc = mapper.modelToView(this.dot);
damage(oldLoc);
} catch (BadLocationException e) {
oldLoc = null;
}
// set the new value of dot
this.dot = dot;
// determine the new location and scroll if
// not visible.
Rectangle newLoc;
try {
newLoc = mapper.modelToView(this.dot);
} catch (BadLocationException e) {
newLoc = null;
}
if (newLoc != null) {
adjustVisibility(newLoc);
}
// repaint the new position
damage(newLoc);
// notify listeners that the caret moved
fireStateChanged();
}
}
/**
* Saves the current caret position. This is used when
* caret up/down actions occur, moving between lines
* that have uneven end positions.
*
* @param p the position
* @see #getMagicCaretPosition
* @see UpAction
* @see DownAction
*/
public void setMagicCaretPosition(Point p) {
magicCaretPosition = p;
}
/**
* Gets the saved caret position.
*
* @return the position
* see #setMagicCaretPosition
*/
public Point getMagicCaretPosition() {
return magicCaretPosition;
}
// --- serialization ---------------------------------------------
private void readObject(ObjectInputStream s)
throws ClassNotFoundException, IOException
{
s.defaultReadObject();
updateHandler = new UpdateHandler();
}
// ---- member variables ------------------------------------------
/**
* The event listener list.
*/
protected EventListenerList listenerList = new EventListenerList();
/**
* The change event for the model.
* Only one ChangeEvent is needed per model instance since the
* event's only (read-only) state is the source property. The source
* of events generated here is always "this".
*/
protected ChangeEvent changeEvent = null;
// package-private to avoid inner classes private member
// access bug
JTextComponent component;
boolean visible;
int dot;
int mark;
Object selectionTag;
Timer flasher;
Point magicCaretPosition;
transient UpdateHandler updateHandler = new UpdateHandler();
class SafeScroller implements Runnable {
SafeScroller(Rectangle r) {
this.r = r;
}
public void run() {
component.scrollRectToVisible(r);
}
Rectangle r;
}
class UpdateHandler implements PropertyChangeListener, DocumentListener, ActionListener {
// --- ActionListener methods ----------------------------------
/**
* Invoked when the blink timer fires.
*
* @param e the action event
*/
public void actionPerformed(ActionEvent e) {
visible = !visible;
Rectangle loc;
try {
TextUI mapper = component.getUI();
loc = mapper.modelToView(dot);
damage(loc);
} catch (BadLocationException bl) {
// hmm....
}
}
// --- DocumentListener methods --------------------------------
/**
* Updates the dot and mark if they were changed by
* the insertion.
*
* @param e the document event
* @see DocumentListener#insertUpdate
*/
public void insertUpdate(DocumentEvent e) {
int adjust = 0;
int offset = e.getOffset();
int length = e.getLength();
if (dot >= offset) {
adjust = length;
}
if (mark >= offset) {
mark += length;
}
if (adjust != 0) {
changeCaretPosition(dot + adjust);
}
}
/**
* Updates the dot and mark if they were changed
* by the removal.
*
* @param e the document event
* @see DocumentListener#removeUpdate
*/
public void removeUpdate(DocumentEvent e) {
int adjust = 0;
int offs0 = e.getOffset();
int offs1 = offs0 + e.getLength();
if (dot >= offs1) {
adjust = offs1 - offs0;
} else if (dot >= offs0) {
adjust = dot - offs0;
}
if (mark >= offs1) {
mark -= offs1 - offs0;
} else if (mark >= offs0) {
mark = offs0;
}
if (mark == (dot - adjust)) {
setDot(dot - adjust);
} else {
changeCaretPosition(dot - adjust);
}
}
/**
* Gives notification that an attribute or set of attributes changed.
*
* @param e the document event
* @see DocumentListener#changedUpdate
*/
public void changedUpdate(DocumentEvent e) {
}
// --- PropertyChangeListener methods -----------------------
/**
* This method gets called when a bound property is changed.
* We are looking for document changes on the editor.
*/
public void propertyChange(PropertyChangeEvent evt) {
Object oldValue = evt.getOldValue();
Object newValue = evt.getNewValue();
if ((oldValue instanceof Document) || (newValue instanceof Document)) {
setDot(0);
if (oldValue != null) {
((Document)oldValue).removeDocumentListener(this);
}
if (newValue != null) {
((Document)newValue).addDocumentListener(this);
}
}
}
}
}